/*************************************************************************
 * @file server.go
 * @auth wuwei
 * @created_time 2018/10/29
 * @brief
 ************************************************************************/

package httplib

/*
#include <stdlib.h>

typedef int (*pfRequest) (int anHandle, char* info);
typedef int (*pfGroup) (char* info);

int DealGroupHandle(void* apfunc, char* info) {
    pfGroup pfunc = (pfGroup) apfunc;
    int isAbort = pfunc(info);
    free(info);
    return isAbort;
}

int DealEventRequest(void* apfunc, int anHandle, char* info) {
    pfRequest pfunc = (pfRequest) apfunc;
    int isBlock = pfunc(anHandle, info);
    free(info);
    return isBlock;
}
*/
import "C"

import (
    "github.com/gin-gonic/gin"
    "sync"
    "unsafe"
    "log"
    "strings"
    "io/ioutil"
    "json"
    "time"
)

type ServerHttp struct {
    svr         *gin.Engine
    address     string
}

type HandleContext struct {
    c           *gin.Context
    block       chan int
    bres        bool
    createTime  time.Time       //if user return failed, over time to return
    lock        sync.Mutex
    locked      bool
}

type RequestInfo struct {
    Url         string
    Ip          string
    Query       map[string]string
    Body        string
}

type FuncInfo struct {
    method      string
    pFunc       unsafe.Pointer
}

type GroupInfo struct {
    group       *gin.RouterGroup 
    callback    unsafe.Pointer
}

var g_bBegin        bool
var g_Svr           *ServerHttp
var g_mapRequest    map[int32]*HandleContext
var g_mapEventLock  sync.Mutex
var g_mapUrlFunc    map[string][]FuncInfo
var g_nMaxTimeOut   int32

var g_mapGroupFunc  map[string]GroupInfo


func init() {
    gin.DisableConsoleColor()
    g_bBegin = false
    g_mapRequest = make(map[int32]*HandleContext)
    g_mapUrlFunc = make(map[string][]FuncInfo)
    g_mapGroupFunc = make(map[string]GroupInfo)
}

/////////////////////////// server /////////////////
func CreateHttpServer(astrAddress string) {
    g_Svr = &ServerHttp{}
    g_Svr.address = astrAddress
    g_Svr.svr = gin.Default()
}

func ServerRun() {
    g_mapEventLock.Lock()
    if g_bBegin {
        g_mapEventLock.Unlock()
        return
    }
    g_bBegin = true
    g_mapEventLock.Unlock()

    g_Svr.svr.Run(g_Svr.address)
}

////////////////////// group func ////////////////////
//func GlobalGroupHandle(c *gin.Context) {
//
//    body, _ := ioutil.ReadAll(c.Request.Body)
//    params := c.Request.URL.Query()
//
//    info := RequestInfo {
//        Url  : c.Request.URL.Path,
//        Ip   : c.ClientIP(),
//        Body : string(body),
//    }
//
//    log.Println("ggggggggggg GlobalGroupHandle :", info.Url)
//    pfunc, ok := g_mapGroupFunc[info.Url]
//
//    if !ok {
//        log.Println("set group func but not find:", info.Url)
//        c.Next()
//        return
//    }
//
//    info.Query = make(map[string]string)
//
//    for k,v := range params {
//        info.Query[k] = v[0]
//    }
//    byInfo, err := json.Marshal(info)
//
//    if nil != err {
//        log.Println("json.Marshal context info failed", info)
//        c.Next()
//        return
//    }
//
//    strInfo := string(byInfo)
//    isAbort := C.DealGroupHandle(pfunc.callback, C.CString(strInfo))
//
//    if 0 != isAbort {
//        c.Abort()
//        return
//    }
//
//    c.Next()
//}

func AddGroup(astrUrl string, callback unsafe.Pointer) int32 {
    if g_bBegin {
        return error_running
    }

    group := g_Svr.svr.Group(astrUrl)
    // can not get group url from Context
    // so we need use no name function to pass the group url which is astrUrl
    group.Use(func (c *gin.Context) {
        log.Println("9999999999999 no name group func, group path:", astrUrl)
        body, _ := ioutil.ReadAll(c.Request.Body)
        params := c.Request.URL.Query()

        info := RequestInfo {
            Url  : c.Request.URL.Path,
            Ip   : c.ClientIP(),
            Body : string(body),
        }

        log.Println("ggggggggggg GlobalGroupHandle :", info.Url)
        //pfunc, ok := g_mapGroupFunc[info.Url]
        pfunc, ok := g_mapGroupFunc[astrUrl]

        if !ok {
            log.Println("set group func but not find:", info.Url)
            c.Next()
            return
        }

        info.Query = make(map[string]string)

        for k,v := range params {
            info.Query[k] = v[0]
        }
        byInfo, err := json.Marshal(info)

        if nil != err {
            log.Println("json.Marshal context info failed", info)
            c.Next()
            return
        }

        strInfo := string(byInfo)
        isAbort := C.DealGroupHandle(pfunc.callback, C.CString(strInfo))

        if 0 != isAbort {
            c.Abort()
            return
        }

        c.Next()
    })
    //group.Use(GlobalGroupHandle)
    g_mapGroupFunc[astrUrl] = GroupInfo{
        group       : group,
        callback    : callback,
    }
    return error_ok
}

func GroupAddRouter(astrMethod, astrGroup, astrUrl string, callback unsafe.Pointer) int32 {
    if g_bBegin {
        return error_running
    }

    group, ok := g_mapGroupFunc[astrGroup]
    if !ok {
        return error_not_find_group
    }

    absUrl := astrGroup + astrUrl
    method := strings.ToUpper(astrMethod)

    if "POST" == method {
        group.group.POST(astrUrl, GlobalHandle)
    } else if "GET" == method {
        group.group.GET(astrUrl, GlobalHandle)
    } else if "PUT" == method {
        group.group.PUT(astrUrl, GlobalHandle)
    } else if "PATCH" == method {
        group.group.PATCH(astrUrl, GlobalHandle)
    } else {
        return error_no_method
    }

    funcInfo := FuncInfo {
        method  : method,
        pFunc   : callback,
    }

    if nil != callback {
        g_mapUrlFunc[absUrl] = append(g_mapUrlFunc[absUrl], funcInfo)
    }
    return error_ok
}


////////////////////// router func ///////////////////
func NewHandleContext(c *gin.Context) *HandleContext{
    context := &HandleContext{}
    context.c = c
    context.block = make(chan int)
    context.createTime = time.Now()
    context.bres = false
    context.locked = false
    return context
}

func GlobalHandle(c *gin.Context) {
    body, _ := ioutil.ReadAll(c.Request.Body)
    params := c.Request.URL.Query()

    info := RequestInfo {
        Url  : c.Request.URL.Path,
        Ip   : c.ClientIP(),
        Body : string(body),
    }

    info.Query = make(map[string]string)

    for k,v := range params {
        info.Query[k] = v[0]
    }
    byInfo, err := json.Marshal(info)

    //log.Println("RequestInfo: ", info)

    hcontext := NewHandleContext(c)
    handle := getNewHandle()
    g_mapEventLock.Lock()
    g_mapRequest[handle] = hcontext
    g_mapEventLock.Unlock()

    pfuncs, ok := g_mapUrlFunc[info.Url]

    if nil == err && ok {
        strInfo := string(byInfo)
        //log.Println("go string info:", strInfo)
        for _, funcInfo := range pfuncs {
            if funcInfo.method != c.Request.Method {
                continue
            }
            isBlock := C.DealEventRequest(funcInfo.pFunc, C.int(handle), C.CString(strInfo))
             if 0 == isBlock && ! hcontext.bres {
                 hcontext.lock.Lock()
                 hcontext.locked = true
                 hcontext.lock.Unlock()
                 <- hcontext.block
             }
        }
    }

    g_mapEventLock.Lock()
    delete(g_mapRequest, handle)
    g_mapEventLock.Unlock()
}

func SetResponseData(anHandle, anStatus, anLock int32, astrResponse string) {
    g_mapEventLock.Lock()
    hcontext, ok := g_mapRequest[anHandle]
    g_mapEventLock.Unlock()

    if ok {
        //log.Println("SetResult : status:", int(anStatus), " resut:", astrResponse);
        hcontext.c.String(int(anStatus), astrResponse)
        hcontext.bres = true
        if anLock > 0 {
            hcontext.lock.Lock()
                if hcontext.locked {
                    hcontext.block <- 1
                }
            hcontext.lock.Unlock()
        }
    } else {
        log.Println("SetResult not find")
    }
    //log.Println("SetResult over")
}

func DefaultResponse(c *gin.Context) {
    c.String(200, "DefaultResponse")
    //log.Println("DefaultResponse")
}


func SetRouter(astrMethod, astrUrl string, handle unsafe.Pointer) int32 {
    if g_bBegin {
        return error_running
    }

    method := strings.ToUpper(astrMethod)

    callfunc := DefaultResponse
    if nil != handle {
        callfunc = GlobalHandle
    }
    

    if "POST" == method {
        g_Svr.svr.POST(astrUrl, callfunc)
    } else if "GET" == method {
        g_Svr.svr.GET(astrUrl, callfunc)
    } else if "PUT" == method {
        g_Svr.svr.PUT(astrUrl, callfunc)
    } else if "PATCH" == method {
        g_Svr.svr.PATCH(astrUrl, callfunc)
    } else {
        return error_no_method
    }


    if nil == handle {
        return error_ok
    }

    funcInfo := FuncInfo {
        method  : method,
        pFunc   : handle,
    }

    if nil != handle {
        g_mapUrlFunc[astrUrl] = append(g_mapUrlFunc[astrUrl], funcInfo)
    }
    return error_ok
}






